require 'digest/md5'
require 'rest_client'
require 'json'

module Installr::Client::Command
  class Deploy < Base
    def deploy_group(app_name, war_file, group_name)
      puts "[deploy:deploy_group] deploy #{app_name} to group #{group_name}" if @options[:verbose]
      @log.info("[deploy:deploy_group] deploy #{app_name} to group #{group_name}")
      agents = @storage.list_agents(group_name)
      if agents
        arr = []
        i = 0
        puts "-- deploy result -----"
        agents.each do
          |agent_name|
          # For the sake of the agent's sshd daemon
          sleep 1
          arr[i] = Thread.new {
            Thread.current["agent"] = agent_name
            Thread.current["result"] = deploy_agent(app_name, war_file, agent_name)
          }
          i = i + 1
        end
        group_result = 0
        arr.each { |t| t.join }
        arr.each do
          |t|
          if t["result"] == -2
            puts "[deploy:deploy_group] redeploying agent #{t["agent"]} ..."
            @log.info("[deploy:deploy_group] redeploying agent #{t["agent"]} ...")
            agent_again_result = deploy_agent(app_name, war_file, t["agent"])
            group_result = -1 if agent_again_result < 0
          elsif t["result"] == -1
            group_result = -1
          end
        end
        puts "---------------------"
        return group_result
      else
        puts "[deploy:deploy_group] cannot find group #{group_name}"
        @log.error("[deploy:deploy_group] cannot find group #{group_name}")
        return -1
      end
    end

    def deploy_agent(app_name, war_file, agent_name)
      puts "[deploy:deploy_agent] deploy #{app_name} to agent #{agent_name}" if @options[:verbose]
      @log.info("[deploy:deploy_agent] deploy #{app_name} to agent #{agent_name}")

      hexdigest = Digest::MD5.file(war_file).hexdigest

      if @storage.check_deployment(agent_name, app_name, hexdigest)
        puts "[deploy:deploy_agent] app #{app_name}(#{hexdigest}) is already deployed to agent #{agent_name}"
        @log.error("[deploy:deploy_agent] app #{app_name}(#{hexdigest}) is already deployed to agent #{agent_name}")
        return -1
      end

      agent = @storage.info_agent(agent_name)

      unless agent
        puts "[deploy:deploy_agent] cannot find agent #{agent_name}"
        @log.error("[deploy:deploy_agent] cannot find agent #{agent_name}")
        return -1
      end

      if @options[:remote_upload_path]
        remote_upload_path = @options[:remote_upload_path]
      else
        remote_upload_path = agent[6]
      end

      if @options[:remote_port]
        remote_port = @options[:remote_port].to_i
      else
        remote_port = agent[5].to_i
      end

      remote_app_dir = remote_upload_path + '/' + app_name
      remote_app_hexdigest_dir = remote_app_dir + '/' + hexdigest;
      remote_file = remote_app_hexdigest_dir + '/' + File.basename(war_file)

      puts "[deploy:deploy_agent] remote file path is [#{remote_file}]" if @options[:verbose]

      if @options[:verbose]
        print "upload to " + agent[0] + "(" + agent[1] + ")"
        if agent[3]
          puts " with certification"
        else
          puts " with password"
        end
      end

      unless port_open?(agent[0], remote_port)
        puts "[deploy:deploy_agent] #{agent_name}: agent port #{remote_port} cannot be connected"
        @log.error("[deploy:deploy_agent] #{agent_name}: agent port #{remote_port} cannot be connected")
        return -2
      end

      unless port_open?(agent[0], 22)
        puts "[deploy:deploy_agent] #{agent_name}: ssh port cannot be connected"
        @log.error("[deploy:deploy_agent] #{agent_name}: ssh port cannot be connected")
        return -2
      end

      puts "[deploy:deploy_agent] #{agent_name}: SFTP connecting..." if @options[:verbose]

      ftp = Installr::SSH::FTP.new(@options)

      if agent[3]
        case ftp.open_with_cert(agent[0], agent[1], agent[3], agent[4])
          when -1
            puts "[deploy:deploy_agent] cannot open agent #{agent_name} with certification"
            @log.error("[deploy:deploy_agent] cannot open agent #{agent_name} with certification")
            return -1
          when -2
            puts "[deploy:deploy_agent] authentication with certification for #{agent_name} is failed"
            @log.error("[deploy:deploy_agent] authentication with certification for #{agent_name} is failed")
            return -1
        end
      else
        case ftp.open(agent[0], agent[1], agent[2])
          when -1
            puts "[deploy:deploy_agent] cannot open agent #{agent_name} with password"
            @log.error("[deploy:deploy_agent] cannot open agent #{agent_name} with password")
            return -1
          when -2
            puts "[deploy:deploy_agent] authentication with password for #{agent_name} is failed"
            @log.error("[deploy:deploy_agent] authentication with password for #{agent_name} is failed")
            return -1
        end
      end

      if ftp.mkdir(remote_app_dir) < 0
        # no problem!
        # application has been deployed already.
      end

      if ftp.mkdir(remote_app_hexdigest_dir) < 0
        # no problem!
        # application has been deployed already.
      end

      puts "[deploy:deploy_agent] #{agent_name}: SFTP uploading..." if @options[:verbose]

      if ftp.upload(war_file, remote_file) < 0
        puts "[deploy:deploy_agent] cannot upload #{war_file} to agent #{agent_name}"
        @log.error("[deploy:deploy_agent] cannot upload #{war_file} to agent #{agent_name}")
        return -1
      end

      ftp.close

      puts "[deploy:deploy_agent] #{agent_name}: Agent deploying..." if @options[:verbose]

      uri = "http://#{agent[0]}:#{remote_port}/app/deploy"
      puts "uri = [#{uri}]" if @options[:verbose]

      resource = RestClient::Resource.new uri
      response = resource.post :app_name => app_name, :war_file => File.basename(war_file), :hexdigest => hexdigest, :remote_file_path => remote_file

      if response.code == 200
        deploy_result = JSON.parse(response.body)
        deploy = deploy_result[0]["result"]
        if deploy
          ts = Time.now.utc.strftime("%Y_%m_%d_%H_%M_%S")
          @storage.create_agent_deployment(agent_name, app_name, ts, File.basename(war_file), hexdigest, remote_file)
          puts "[deploy:deploy_agent] #{agent_name}: #{deploy}"
          return 0
        else
          puts "[deploy:deploy_agent] deploying agent #{agent_name} of app #{app_name} is failed" 
          @log.error("[deploy:deploy_agent] deploying agent #{agent_name} of app #{app_name} is failed")
        end
      else
        puts "[deploy:deploy_agent] response code = #{response.code} from agent #{agent_name}"
        @log.error("[deploy:deploy_agent] response code #{response.code} from agent #{agent_name}")
        return -1
      end

      rescue => e
        puts "[deploy:deploy_agent] #{agent_name}: (exception) #{e}"
        @log.error("[deploy:deploy_agent] #{agent_name}: (exception) #{e}")
        return -2
    end

    def undeploy_group(app_name, group_name)
      puts "[deploy:undeploy_group] undeploy #{app_name} from group #{group_name}" if @options[:verbose]
      @log.info("[deploy:undeploy_group] undeploy #{app_name} from group #{group_name}")

      agents = @storage.list_agents(group_name)
      if agents
        group_result = 0
        puts "-- undeploy result -----"
        agents.each do
          |agent_name|
          agent_result = undeploy_agent(app_name, agent_name)
          if agent_result == -2
            puts "[deploy:undeploy_group] #{agent_name}: retrying once again"
            @log.error("[deploy:undeploy_group] #{agent_name}: retrying once again")
            agent_result = undeploy_agent(app_name, agent_name)
          end
          group_result = agent_result if agent_result < 0
        end
        puts "---------------------"
        return group_result
      else
        puts "[deploy:undeploy_group] cannot find group #{group_name}"
        @log.error("[deploy:undeploy_group] cannot find group #{group_name}")
        return -1
      end
    end

    def undeploy_agent(app_name, agent_name)
      puts "[deploy:undeploy_agent] undeploy #{app_name} from agent #{agent_name}" if @options[:verbose]
      @log.info("[deploy:undeploy_agent] undeploy #{app_name} from agent #{agent_name}")

      unless @storage.check_app(app_name, agent_name)
        puts "[deploy:undeploy_agent] #{agent_name}: cannot find app #{app_name}"
        @log.error("[deploy:undeploy_agent] #{agent_name}: cannot find app #{app_name}")
        return -1
      end

      agent = @storage.info_agent(agent_name)
      unless agent
        puts "[deploy:undeploy_agent] cannot find agent #{agent_name}"
        @log.error("[deploy:undeploy_agent] cannot find agent #{agent_name}")
        return -1
      end

      if @options[:remote_port]
        remote_port = @options[:remote_port].to_i
      else
        remote_port = agent[5].to_i
      end

      unless port_open?(agent[0], remote_port)
        puts "[deploy:undeploy_agent] #{agent_name}: agent port #{remote_port} cannot be connected"
        @log.error("[deploy:undeploy_agent] #{agent_name}: agent port #{remote_port} cannot be connected")
        return -2
      end

      uri = "http://#{agent[0]}:#{remote_port}/app/undeploy"
      puts "uri = [#{uri}]" if @options[:verbose]

      resource = RestClient::Resource.new uri
      response = resource.post :app_name => app_name

      if response.code == 200
        undeploy_result = JSON.parse(response.body)
        undeploy = undeploy_result[0]["result"]
        if undeploy
          @storage.delete_agent_deployment(agent_name, app_name)
          puts "[deploy:undeploy_agent] #{agent_name}: #{undeploy}"
        else
          puts "[deploy:undeploy_agent] undeploying agent #{agent_name} of app #{app_name} is failed" 
          @log.error("[deploy:undeploy_agent] undeploying agent #{agent_name} of app #{app_name} is failed")
        end
        return 0
      else
        puts "[deploy:undeploy_agent] response code = #{response.code} from agent #{agent_name}"
        @log.error("[deploy:undeploy_agent] response code #{response.code} from agent #{agent_name}")
        return -1
      end

      rescue => e
        puts "[deploy:undeploy_agent] #{agent_name}: (exception) #{e}"
        @log.error("[deploy:undeploy_agent] #{agent_name}: (exception) #{e}")
        return -2
    end

    def activate_group(app_name, group_name)
      puts "[deploy:activate_group] activate #{app_name} from group #{group_name}" if @options[:verbose]
      @log.info("[deploy:activate_group] activate #{app_name} from group #{group_name}")

      agents = @storage.list_agents(group_name)
      if agents
        group_result = 0
        puts "-- activate result -----"
        agents.each do
          |agent_name|
          agent_result = activate_agent(app_name, agent_name)
          if agent_result == -2
            puts "[deploy:activate_group] #{agent_name}: retrying once again"
            @log.error("[deploy:activate_group] #{agent_name}: retrying once again")
            agent_result = activate_agent(app_name, agent_name)
          end
          group_result = agent_result if agent_result < 0
        end
        puts "---------------------"
        return group_result
      else
        puts "[deploy:activate_group] cannot find group #{group_name}"
        @log.error("[deploy:activate_group] cannot find group #{group_name}")
        return -1
      end
    end

    def activate_agent(app_name, agent_name)
      puts "[deploy:activate_agent] activate #{app_name} from agent #{agent_name}" if @options[:verbose]
      @log.info("[deploy:activate_agent] activate #{app_name} from agent #{agent_name}")

      unless @storage.check_app(app_name, agent_name)
        puts "[deploy:activate_agent] #{agent_name}: cannot find app #{app_name}"
        @log.error("[deploy:activate_agent] #{agent_name}: cannot find app #{app_name}")
        return -1
      end

      agent = @storage.info_agent(agent_name)
      unless agent
        puts "[deploy:activate_agent] cannot find agent #{agent_name}"
        @log.error("[deploy:activate_agent] cannot find agent #{agent_name}")
        return -1
      end

      app = @storage.info_agent_deployment(agent_name, app_name, '0')
      unless app
        puts "[deploy:activate_agent] #{agent_name}: cannot find app #{app_name}"
        @log.error("[deploy:activate_agent] #{agent_name}: cannot find app #{app_name}")
        return -1
      end

      if @options[:remote_port]
        remote_port = @options[:remote_port].to_i
      else
        remote_port = agent[5].to_i
      end
      
      unless port_open?(agent[0], remote_port)
        puts "[deploy:activate_agent] #{agent_name}: agent port #{remote_port} cannot be connected"
        @log.error("[deploy:activate_agent] #{agent_name}: agent port #{remote_port} cannot be connected")
        return -2
      end

      uri = "http://#{agent[0]}:#{remote_port}/app/activate"
      puts "uri = [#{uri}]" if @options[:verbose]

      resource = RestClient::Resource.new uri
      response = resource.post :app_name => app_name, :hexdigest => app[2]

      if response.code == 200
        activate_result = JSON.parse(response.body)
        activate = activate_result[0]["result"]
        if activate
          puts "[deploy:activate_agent] #{agent_name}: #{activate}"
        else
          puts "[deploy:activate_agent] activating agent #{agent_name} of app #{app_name} is failed" 
          @log.error("[deploy:activate_agent] activating agent #{agent_name} of app #{app_name} is failed")
        end
        return 0
      else
        puts "[deploy:activate_agent] response code = #{response.code} from agent #{agent_name}"
        @log.error("[deploy:activate_agent] response code #{response.code} from agent #{agent_name}")
        return -1
      end

      rescue => e
        puts "[deploy:activate_agent] #{agent_name}: (exception) #{e}"
        @log.error("[deploy:activate_agent] #{agent_name}: (exception) #{e}")
        return -2
    end

    def deactivate_group(app_name, group_name)
      puts "[deploy:deactivate_group] deactivate #{app_name} from group #{group_name}" if @options[:verbose]
      @log.info("[deploy:deactivate_group] deactivate #{app_name} from group #{group_name}")

      agents = @storage.list_agents(group_name)
      if agents
        group_result = 0
        puts "-- deactivate result -----"
        agents.each do
          |agent_name|
          agent_result = deactivate_agent(app_name, agent_name)
          if agent_result == -2
            puts "[deploy:deactivate_group] #{agent_name}: retrying once again"
            @log.error("[deploy:deactivate_group] #{agent_name}: retrying once again")
            agent_result = deactivate_agent(app_name, agent_name)
          end
          group_result = agent_result if agent_result < 0
        end
        puts "---------------------"
        return group_result
      else
        puts "[deploy:deactivate_group] cannot find group #{group_name}"
        @log.error("[deploy:deactivate_group] cannot find group #{group_name}")
        return -1
      end
    end

    def deactivate_agent(app_name, agent_name)
      puts "[deploy:deactivate_agent] deactivate #{app_name} from agent #{agent_name}" if @options[:verbose]
      @log.info("[deploy:deactivate_agent] deactivate #{app_name} from agent #{agent_name}")

      unless @storage.check_app(app_name, agent_name)
        puts "[deploy:deactivate_agent] #{agent_name}: cannot find app #{app_name}"
        @log.error("[deploy:deactivate_agent] #{agent_name}: cannot find app #{app_name}")
        return -1
      end

      agent = @storage.info_agent(agent_name)
      unless agent
        puts "[deploy:deactivate_agent] cannot find agent #{agent_name}"
        @log.error("[deploy:deactivate_agent] cannot find agent #{agent_name}")
        return -1
      end

      if @options[:remote_port]
        remote_port = @options[:remote_port].to_i
      else
        remote_port = agent[5].to_i
      end
      
      unless port_open?(agent[0], remote_port)
        puts "[deploy:deactivate_agent] #{agent_name}: agent port #{remote_port} cannot be connected"
        @log.error("[deploy:deactivate_agent] #{agent_name}: agent port #{remote_port} cannot be connected")
        return -2
      end

      uri = "http://#{agent[0]}:#{remote_port}/app/deactivate"
      puts "uri = [#{uri}]" if @options[:verbose]

      resource = RestClient::Resource.new uri
      response = resource.post :app_name => app_name

      if response.code == 200
        deactivate_result = JSON.parse(response.body)
        deactivate = deactivate_result[0]["result"]
        if deactivate
          puts "[deploy:deactivate_agent] #{agent_name}: #{deactivate}"
        else
          puts "[deploy:deactivate_agent] deactivating agent #{agent_name} of app #{app_name} is failed" 
          @log.error("[deploy:deactivate_agent] deactivating agent #{agent_name} of app #{app_name} is failed")
        end
        return 0
      else
        puts "[deploy:deactivate_agent] response code = #{response.code} from agent #{agent_name}"
        @log.error("[deploy:deactivate_agent] response code #{response.code} from agent #{agent_name}")
        return -1
      end

      rescue => e
        puts "[deploy:deactivate_agent] #{agent_name}: (exception) #{e}"
        @log.error("[deploy:deactivate_agent] #{agent_name}: (exception) #{e}")
        return -2
    end

    def start_group(app_name, group_name)
      puts "[deploy:start_group] start #{app_name} from group #{group_name}" if @options[:verbose]
      @log.info("[deploy:start_group] start #{app_name} from group #{group_name}")

      agents = @storage.list_agents(group_name)
      if agents
        group_result = 0
        puts "-- start result -----"
        agents.each do
          |agent_name|
          agent_result = start_agent(app_name, agent_name)
          if agent_result == -2
            puts "[deploy:start_group] #{agent_name}: retrying once again"
            @log.error("[deploy:start_group] #{agent_name}: retrying once again")
            agent_result = start_agent(app_name, agent_name)
          end
          group_result = agent_result if agent_result < 0
        end
        puts "---------------------"
        return group_result
      else
        puts "[deploy:start_group] cannot find group #{group_name}"
        @log.error("[deploy:start_group] cannot find group #{group_name}")
        return -1
      end
    end

    def start_agent(app_name, agent_name)
      puts "[deploy:start_agent] start #{app_name} from agent #{agent_name}" if @options[:verbose]
      @log.info("[deploy:start_agent] start #{app_name} from agent #{agent_name}")

      unless @storage.check_app(app_name, agent_name)
        puts "[deploy:start_agent] #{agent_name}: cannot find app #{app_name}"
        @log.error("[deploy:start_agent] #{agent_name}: cannot find app #{app_name}")
        return -1
      end

      agent = @storage.info_agent(agent_name)
      unless agent
        puts "[deploy:start_agent] cannot find agent #{agent_name}"
        @log.error("[deploy:start_agent] cannot find agent #{agent_name}")
        return -1
      end

      if @options[:remote_port]
        remote_port = @options[:remote_port].to_i
      else
        remote_port = agent[5].to_i
      end

      unless port_open?(agent[0], remote_port)
        puts "[deploy:start_agent] #{agent_name}: agent port #{remote_port} cannot be connected"
        @log.error("[deploy:start_agent] #{agent_name}: agent port #{remote_port} cannot be connected")
        return -2
      end

      uri = "http://#{agent[0]}:#{remote_port}/app/start"
      puts "uri = [#{uri}]" if @options[:verbose]

      resource = RestClient::Resource.new uri
      response = resource.post :app_name => app_name

      if response.code == 200
        start_result = JSON.parse(response.body)
        start = start_result[0]["result"]
        if start
          puts "[deploy:start_agent] #{agent_name}: #{start}"
        else
          puts "[deploy:start_agent] #{agent_name}: starting app #{app_name} is failed" 
          @log.error("[deploy:start_agent] #{agent_name}: starting app #{app_name} is failed")
        end
        return 0
      else
        puts "[deploy:start_agent] response code = #{response.code} from agent #{agent_name}"
        @log.error("[deploy:start_agent] response code #{response.code} from agent #{agent_name}")
        return -1
      end

      rescue => e
        puts "[deploy:start_agent] #{agent_name}: (exception) #{e}"
        @log.error("[deploy:start_agent] #{agent_name}: (exception) #{e}")
        return -2
    end

    def stop_group(app_name, group_name)
      puts "[deploy:stop_group] stop #{app_name} from group #{group_name}" if @options[:verbose]
      @log.info("[deploy:stop_group] stop #{app_name} from group #{group_name}")

      agents = @storage.list_agents(group_name)
      if agents
        group_result = 0
        puts "-- start result -----"
        agents.each do
          |agent_name|
          agent_result = stop_agent(app_name, agent_name)
          if agent_result == -2
            puts "[deploy:stop_group] #{agent_name}: retrying once again"
            @log.error("[deploy:stop_group] #{agent_name}: retrying once again")
            agent_result = stop_agent(app_name, agent_name)
          end
          group_result = agent_result if agent_result < 0
        end
        puts "---------------------"
        return group_result
      else
        puts "[deploy:stop_group] cannot find group #{group_name}"
        @log.error("[deploy:stop_group] cannot find group #{group_name}")
        return -1
      end
    end

    def stop_agent(app_name, agent_name)
      puts "[deploy:stop_agent] stop #{app_name} from agent #{agent_name}" if @options[:verbose]
      @log.info("[deploy:stop_agent] stop #{app_name} from agent #{agent_name}")

      unless @storage.check_app(app_name, agent_name)
        puts "[deploy:stop_agent] #{agent_name}: cannot find app #{app_name}"
        @log.error("[deploy:stop_agent] #{agent_name}: cannot find app #{app_name}")
        return -1
      end

      agent = @storage.info_agent(agent_name)
      unless agent
        puts "[deploy:stop_agent] cannot find agent #{agent_name}"
        @log.error("[deploy:stop_agent] cannot find agent #{agent_name}")
        return -1
      end

      if @options[:remote_port]
        remote_port = @options[:remote_port].to_i
      else
        remote_port = agent[5].to_i
      end

      unless port_open?(agent[0], remote_port)
        puts "[deploy:stop_agent] #{agent_name}: agent port #{remote_port} cannot be connected"
        @log.error("[deploy:stop_agent] #{agent_name}: agent port #{remote_port} cannot be connected")
        return -2
      end

      uri = "http://#{agent[0]}:#{remote_port}/app/stop"
      puts "uri = [#{uri}]" if @options[:verbose]

      resource = RestClient::Resource.new uri
      response = resource.post :app_name => app_name

      if response.code == 200
        stop_result = JSON.parse(response.body)
        stop = stop_result[0]["result"]
        if stop
          puts "[deploy:stop_agent] #{agent_name}: #{stop}"
        else
          puts "[deploy:stop_agent] #{agent_name}: stopping app #{app_name} is failed" 
          @log.error("[deploy:stop_agent] #{agent_name}: stopping app #{app_name} is failed")
        end
        return 0
      else
        puts "[deploy:stop_agent] response code = #{response.code} from agent #{agent_name}"
        @log.error("[deploy:stop_agent] response code #{response.code} from agent #{agent_name}")
        return -1
      end

      rescue => e
        puts "[deploy:stop_agent] #{agent_name}: (exception) #{e}"
        @log.error("[deploy:stop_agent] #{agent_name}: (exception) #{e}")
        return -2
    end

    def apps
      puts "[deploy:apps] list apps" if @options[:verbose]
      @log.info("[deploy:apps] list apps")

      objs = @storage.list_apps
      objs.uniq.each { |obj| print obj.chomp('/') + ", " }
      puts "---"
      return 0
    end

    def history_local(app_name)
      puts "[deploy:history_local] history #{app_name} from local" if @options[:verbose]
      @log.info("[deploy:history_local] history #{app_name} from local")

      histories = @storage.history_app_deployment(app_name)
      puts "------ #{app_name} --------------------"
      histories.each { |obj| puts "timestamp: #{obj[0]}, file: #{obj[1]}, md5hash: #{obj[2]}, agent: #{obj[3]}" }
      puts "--------------------------------------"
      if @options[:initialize]
        puts "Warning: initialize option is turned on, so all the app app_name will be deleted."
        @log.warn("[deploy:history_local] Warning: initialize option is turned on, so all the app app_name will be deleted.")
        @storage.delete_app_deployment(app_name)
      end
      return 0
    end

    def history_group(app_name, group_name)
      puts "[deploy:history_group] history #{app_name} from group #{group_name}" if @options[:verbose]
      @log.info("[deploy:history_group] history #{app_name} from group #{group_name}")

      agents = @storage.list_agents(group_name)
      if agents
        group_result = 0
        puts "-- history result -----"
        agents.each do
          |agent_name|
          agent_result = history_agent(app_name, agent_name)
          if agent_result == -2
            puts "[deploy:history_group] #{agent_name}: retrying once again"
            @log.error("[deploy:history_group] #{agent_name}: retrying once again")
            agent_result = history_agent(app_name, agent_name)
          end
          group_result = agent_result if agent_result < 0
        end
        puts "---------------------"
        return group_result
      else
        puts "[deploy:history_group] cannot find group #{group_name}"
        @log.error("[deploy:history_group] cannot find group #{group_name}")
        return -1
      end
    end

    def history_agent(app_name, agent_name)
      puts "[deploy:history_agent] history #{app_name} from agent #{agent_name}" if @options[:verbose]
      @log.info("[deploy:history_agent] history #{app_name} from agent #{agent_name}")

      unless @storage.check_app(app_name, agent_name)
        puts "[deploy:history_agent] #{agent_name}: cannot find app #{app_name}"
        @log.error("[deploy:history_agent] #{agent_name}: cannot find app #{app_name}")
        return -1
      end

      agent = @storage.info_agent(agent_name)
      unless agent
        puts "[deploy:history_agent] cannot find agent #{agent_name}"
        @log.error("[deploy:history_agent] cannot find agent #{agent_name}")
        return -1
      end

      if @options[:remote_port]
        remote_port = @options[:remote_port].to_i
      else
        remote_port = agent[5].to_i
      end

      unless port_open?(agent[0], remote_port)
        puts "[deploy:history_agent] #{agent_name}: agent port #{remote_port} cannot be connected"
        @log.error("[deploy:history_agent] #{agent_name}: agent port #{remote_port} cannot be connected")
        return -2
      end

      uri = "http://#{agent[0]}:#{remote_port}/app/history/#{app_name}"
      puts "uri = [#{uri}]" if @options[:verbose]

      resource = RestClient::Resource.new uri
      response = resource.get

      if response.code == 200
        histories_result = JSON.parse(response.body)
        histories = histories_result[0]["result"]
        puts "------ #{agent_name} --------------------"
        histories.each { |history| puts "timestamp: #{history[0]}, file: #{history[1]}, md5hash: #{history[2]}" }
        puts "--------------------------------------"
        return 0
      else
        puts "[deploy:history_agent] response code = #{response.code} from agent #{agent_name}"
        @log.error("[deploy:history_agent] response code #{response.code} from agent #{agent_name}")
        return -1
      end

      rescue => e
        puts "[deploy:history_agent] #{agent_name}: (exception) #{e}"
        @log.error("[deploy:history_agent] #{agent_name}: (exception) #{e}")
        return -2
    end

    def head_group(app_name, app_md5hash, group_name)
      puts "[deploy:head_group] head #{app_name} #{app_md5hash} from group #{group_name}" if @options[:verbose]
      @log.info("[deploy:head_group] head #{app_name} #{app_md5hash} from group #{group_name}")

      agents = @storage.list_agents(group_name)
      if agents
        group_result = 0
        puts "-- head result -----"
        agents.each do
          |agent_name|
          agent_result = head_agent(app_name, app_md5hash, agent_name)
          if agent_result == -2
            puts "[deploy:head_group] #{agent_name}: retrying once again"
            @log.error("[deploy:head_group] #{agent_name}: retrying once again")
            agent_result = head_agent(app_name, app_md5hash, agent_name)
          end
          group_result = agent_result if agent_result < 0
        end
        puts "---------------------"
        return group_result
      else
        puts "[deploy:head_group] cannot find group #{group_name}"
        @log.error("[deploy:head_group] cannot find group #{group_name}")
        return -1
      end
    end

    def head_agent(app_name, app_md5hash, agent_name)
      puts "[deploy:head_agent] head #{app_name} #{app_md5hash} from agent #{agent_name}" if @options[:verbose]
      @log.info("[deploy:head_agent] head #{app_name} #{app_md5hash} from agent #{agent_name}")

      unless @storage.check_app(app_name, agent_name)
        puts "[deploy:head_agent] #{agent_name}: cannot find app #{app_name}"
        @log.error("[deploy:head_agent] #{agent_name}: cannot find app #{app_name}")
        return -1
      end

      unless @storage.info_agent_deployment(agent_name, app_name, app_md5hash)
        puts "[deploy:head_agent] #{agent_name}: cannot find app_md5hash #{app_md5hash} in the deployment history"
        @log.error("[deploy:head_agent] #{agent_name}: cannot find app_md5hash #{app_md5hash} in the deployment_history")
       return -1
      end

      agent = @storage.info_agent(agent_name)
      unless agent
        puts "[deploy:head_agent] cannot find agent #{agent_name}"
        @log.error("[deploy:head_agent] cannot find agent #{agent_name}")
        return -1
      end

      if @options[:remote_port]
        remote_port = @options[:remote_port].to_i
      else
        remote_port = agent[5].to_i
      end

      unless port_open?(agent[0], remote_port)
        puts "[deploy:head_agent] #{agent_name}: agent port #{remote_port} cannot be connected"
        @log.error("[deploy:head_agent] #{agent_name}: agent port #{remote_port} cannot be connected")
        return -2
      end

      uri = "http://#{agent[0]}:#{remote_port}/app/head"
      puts "uri = [#{uri}]" if @options[:verbose]

      resource = RestClient::Resource.new uri
      response = resource.post :app_name => app_name, :app_md5hash => app_md5hash

      if response.code == 200
        head_result = JSON.parse(response.body)
        head = head_result[0]["result"]
        if head
          puts "[deploy:head_agent] #{agent_name}: #{head}"
          if head == "Successfully changed"
            @storage.change_agent_deployment(agent_name, app_name, app_md5hash)
          end
        else
          puts "[deploy:head_agent] #{agent_name}: heading of app #{app_name} with #{app_md5hash} is failed" 
          @log.error("[deploy:head_agent] #{agent_name}: heading of app #{app_name} with #{app_md5hash} is failed")
        end
        return 0
      else
        puts "[deploy:head_agent] response code = #{response.code} from agent #{agent_name}"
        @log.error("[deploy:head_agent] response code #{response.code} from agent #{agent_name}")
        return -1
      end

      rescue => e
        puts "[deploy:head_agent] #{agent_name}: (exception) #{e}"
        @log.error("[deploy:head_agent] #{agent_name}: (exception) #{e}")
        return -2
    end

    def info_head_from_local(app_name)
      puts "[deploy:info_head_from_local] info head #{app_name} from local" if @options[:verbose]
      @log.info("[deploy:info_head_from_local] info head #{app_name} from local")

      heads = @storage.history_app_deployment(app_name, 0)
      if heads.length != 0
        heads.each { |obj| puts "current local information of war file #{obj[1]} is #{obj[2]} for #{obj[3]} at #{obj[0]}" }
      else
        puts "[deploy:info_head_from_local] information for app #{app_name} is not available"
        @log.warn("[deploy:info_head_from_local] information for app #{app_name} is not available")
      end
      return 0
    end

    def info_head_from_group(app_name, group_name)
      puts "[deploy:info_head_from_group] info head #{app_name} from group #{group_name}" if @options[:verbose]
      @log.info("[deploy:info_head_from_group] info head #{app_name} from group #{group_name}")

      agents = @storage.list_agents(group_name)
      if agents
        group_result = 0
        puts "-- info_head result -----"
        agents.each do
          |agent_name|
          agent_result = info_head_from_agent(app_name, agent_name)
          if agent_result == -2
            puts "[deploy:info_head_from_group] #{agent_name}: retrying once again"
            @log.error("[deploy:info_head_from_group] #{agent_name}: retrying once again")
            agent_result = info_head_from_agent(app_name, agent_name)
          end
          group_result = agent_result if agent_result < 0
        end
        puts "---------------------"
        return group_result
      else
        puts "[deploy:info_head_from_group] cannot find group #{group_name}"
        @log.error("[deploy:info_head_from_group] cannot find group #{group_name}")
        return -1
      end
    end

    def info_head_from_agent(app_name, agent_name)
      puts "[deploy:info_head_from_agent] info head #{app_name} from agent #{agent_name}" if @options[:verbose]
      @log.info("[deploy:info_head_from_agent] info head #{app_name} from agent #{agent_name}")

      unless @storage.check_app(app_name, agent_name)
        puts "[deploy:info_head_from_agent] #{agent_name}: cannot find app #{app_name}"
        @log.error("[deploy:info_head_from_agent] #{agent_name}: cannot find app #{app_name}")
        return -1
      end

      agent = @storage.info_agent(agent_name)
      unless agent
        puts "[deploy:info_head_from_agent] cannot find agent #{agent_name}"
        @log.error("[deploy:info_head_from_agent] cannot find agent #{agent_name}")
        return -1
      end

      if @options[:remote_port]
        remote_port = @options[:remote_port].to_i
      else
        remote_port = agent[5].to_i
      end

      unless port_open?(agent[0], remote_port)
        puts "[deploy:info_head_from_agent] #{agent_name}: agent port #{remote_port} cannot be connected"
        @log.error("[deploy:info_head_from_agent] #{agent_name}: agent port #{remote_port} cannot be connected")
        return -2
      end

      uri = "http://#{agent[0]}:#{remote_port}/app/info/#{app_name}"
      puts "uri = [#{uri}]" if @options[:verbose]

      resource = RestClient::Resource.new uri
      response = resource.get

      if response.code == 200
        head_result = JSON.parse(response.body)
        head = head_result[0]["result"]
        if head
          puts "[deploy:info_head_from_agent] #{agent_name}: current remote information of war file #{head[1]} is #{head[2]} at #{head[0]}" 
        else
          puts "[deploy:info_head_from_agent] information for app #{app_name} is not available"
          @log.warn("[deploy:info_head_from_agent] information for app #{app_name} is not available.")
        end
        return 0
      else
        puts "[deploy:info_head_from_agent] response code = #{response.code} from agent #{agent_name}"
        @log.error("[deploy:info_head_from_agent] response code #{response.code} from agent #{agent_name}")
        return -1
      end

      rescue => e
        puts "[deploy:info_head_from_agent] #{agent_name}: #{e}"
        @log.error("[deploy:info_head_from_agent] #{agent_name}: #{e}")
        return -2
    end
  end
end
